package com.sprometheus.core.pojo;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.sprometheus.core.constants.SPrometheusTitleEnum;
import com.sprometheus.core.anno.Msg;
import com.sprometheus.core.constants.SConstants;
import com.sprometheus.core.constants.Scene;
import com.sprometheus.core.constants.SendChannel;
import com.sprometheus.strategy.RoutingStrategy;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.slf4j.MDC;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.toList;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GlobalExceptionBO {
    @Msg(msg = "接口耗时")
    private String consumingTime;
    //调用者的类和方法
    private Method callerMethod;
    private Class<?> callerClass;
    @Msg(msg = "uri 接口地址")
    private String uri;
    @Msg(msg = "飞书群webhook")
    private String groupUrl;
    @Msg(msg = "场景 业务人员/开发人员")
    private String scene;
    @Msg(msg = "请求IP")
    private String ip;
    @Msg(msg = "请求参数")
    private String reqData;
    @Msg(msg = "响应编码")
    private Integer code;
    @Msg(msg = "异常时间")
    private String expDateTime;
    @Msg(msg = "类路径")
    private String classPath;
    @Msg(msg = "异常信息")
    private String exceptionMessage;
    @Msg(msg = "异常追踪信息")
    private String traceInfo;
    @Msg(msg = "链路ID")
    private String traceId;
    @Msg(msg = "项目名称")
    private String serverName;
    @Msg(msg = "环境")
    private String active;
    @Msg(msg = "功能名称")
    private String title;
    @Msg(msg = "监控业务类型")
    private String monitorType;
    @Msg(msg = "是否@所有人 @all")
    private Integer atAll = SConstants.NO;
    @Msg(msg = "推送媒介 1:飞书(默认) 2:钉钉 3:邮件")
    private String sendChannel = SendChannel.FEISHU;

    public GlobalExceptionBO(SPrometheusException e, RoutingStrategy routingStrategy) {
        this.code = e.getCode();
        this.exceptionMessage = e.getMessage();
        this.title = e.getTitle();
        this.monitorType = e.getType();
        this.expDateTime = DateUtil.now();
        this.traceId = MDC.get(SConstants.TRACE_ID);
        List<StackTraceElement> list = stackTraceList(e, routingStrategy);
        List<String> listCharSequence = new LinkedList<>();
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(v -> listCharSequence.add(v.toString()));
            this.traceInfo = String.join("\r\n", listCharSequence);
            this.classPath = list.get(0).toString();
        }
    }

    public GlobalExceptionBO(Exception e, RoutingStrategy routingStrategy) {
        this.exceptionMessage = e.getMessage();
        this.expDateTime = DateUtil.now();
        this.traceId = MDC.get(SConstants.TRACE_ID);
        List<StackTraceElement> list = stackTraceList(e, routingStrategy);
        List<String> listCharSequence = new LinkedList<>();
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(v -> listCharSequence.add(v.toString()));
            this.traceInfo = String.join("\r\n", listCharSequence);
            this.classPath = list.get(0).toString();
        }
    }

    public GlobalExceptionBO(Throwable e, RoutingStrategy routingStrategy) {
        this.exceptionMessage = e.toString();
        this.expDateTime = DateUtil.now();
        this.traceId = MDC.get(SConstants.TRACE_ID);
        List<StackTraceElement> list = stackTraceList(e, routingStrategy);
        List<String> listCharSequence = new LinkedList<>();
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(v -> listCharSequence.add(v.toString()));
            this.traceInfo = String.join("\r\n", listCharSequence);
            this.classPath = list.get(0).toString();
        }
    }

    /***
     * 手动创建消息通知构造方法
     * @param code              异常code码
     * @param exceptionMessage  异常描述
     * @param reqData           请求参数
     */
    public GlobalExceptionBO(Integer code, String exceptionMessage, String reqData) {
        Thread thread = Thread.currentThread();
        StackTraceElement[] stackTrace = thread.getStackTrace();
        //获取调用者所在的栈信息
        StackTraceElement element = stackTrace[stackTrace.length - 1];
        this.code = code;
        this.exceptionMessage = exceptionMessage;
        this.expDateTime = DateUtil.now();
        this.traceId = MDC.get(SConstants.TRACE_ID);
        this.reqData = reqData;
        this.classPath = element.getClassName() + "#" + element.getMethodName() + ":" + element.getLineNumber();
        this.traceInfo = classPath;
        this.callerClass = getCallerClass(element.getClassName(), thread);
        this.callerMethod = getCallerMethod(element.getMethodName());
    }

    public GlobalExceptionBO(Exp req, RoutingStrategy routingStrategy) {
        SPrometheusTitleEnum titleEnum = req.getTitleEnum();
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        this.sendChannel = req.getSendChannel();
        this.atAll = req.getAtAll();
        this.title = titleEnum.getTitle();
        this.uri = req.getUri();
        this.code = titleEnum.getCode();
        this.monitorType = titleEnum.getType();
        this.exceptionMessage = req.getMsg();
        this.expDateTime = DateUtil.now();
        this.traceId = MDC.get(SConstants.TRACE_ID);
        this.reqData = req.getReqData();
        List<StackTraceElement> elements = Arrays.asList(stackTrace);
        List<StackTraceElement> list = addStackTrace(elements, routingStrategy.getIncludedTracePackage());
        List<String> listCharSequence = new LinkedList<>();
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(v -> listCharSequence.add(v.toString()));
            this.traceInfo = String.join("\r\n", listCharSequence);
            this.classPath = list.get(0).toString();
        }
    }

    private Class<?> getCallerClass(String c, Thread t) {
        ClassLoader cl = t.getContextClassLoader();
        try {
            return cl.loadClass(c);
        } catch (ClassNotFoundException e) {
        }
        return null;
    }

    private Method getCallerMethod(String m) {
        for (Method method : callerClass.getDeclaredMethods()) {
            if (m.equals(method.getName())) {
                return method;
            }
        }
        return null;
    }


    /***
     * 堆栈信息
     * @param throwable         异常
     * @param routingStrategy   筛选配置
     * @return
     */
    private List<StackTraceElement> stackTraceList(Throwable throwable, RoutingStrategy routingStrategy) {
        List<StackTraceElement> list = new LinkedList<>();
        Set<String> includedTracePackage = routingStrategy.getIncludedTracePackage();
        addStackTrace(list, throwable, includedTracePackage);
        return list;
    }

    public String createText() {
        //开发者信息
        StringBuilder text = new StringBuilder();
        //业务异常只关注问题本身
        if (Scene.BUSINESS.toString().equalsIgnoreCase(scene)) {
            text.append("监控类型：").append(monitorType).append("\r\n");
            text.append("监控业务名称：").append(title).append("\r\n");
            text.append("描述：").append(exceptionMessage).append("\r\n");
            text.append("触发时间：").append(expDateTime).append("\r\n");
            text.append("服务名称：").append(serverName).append("\r\n");
            text.append("链路id：").append(traceId).append("\r\n");
            if (SConstants.YES.equals(atAll)) {
                text.append("<at user_id=\"all\"></at>").append("\r\n");
            }
        } else if (Scene.ORIGIN_MSG.toString().equalsIgnoreCase(scene)) {
            return exceptionMessage;
        } else {
            //开发异常主要关注报错位置
            text.append("服务名称：").append(serverName).append("\r\n");
            text.append("请求地址：").append(uri).append("\r\n");
            text.append("接口描述：").append(monitorType).append("|").append(title).append("\r\n");
            text.append("异常信息：").append(exceptionMessage).append("\r\n");
            text.append("异常追踪：").append(traceInfo).append("\r\n");
            text.append("错误码：").append(code).append("\r\n");
            text.append("异常定位：").append(classPath).append("\r\n");
            text.append("链路id：").append(traceId).append("\r\n");
            text.append("异常时间：").append(expDateTime).append("\r\n");
            text.append("接口耗时：").append(consumingTime).append("\r\n");
            text.append("请求IP：").append(ip).append("\r\n");
            text.append("active：").append(active).append("\r\n");
            text.append("请求参数：").append(reqData).append("\r\n");
            if (SConstants.YES.equals(atAll)) {
                text.append("<at user_id=\"all\"></at>").append("\r\n");
            }
        }
        return text.toString();
    }

    private void addStackTrace(List<StackTraceElement> list, Throwable throwable, Set<String> includeTrace) {
        list.addAll(0, Arrays.stream(throwable.getStackTrace())
                .filter(x -> include(includeTrace, x))
                .filter(x -> !"<generated>".equals(x.getFileName())
                        && !x.getClassName().startsWith("com.sprometheus"))
                .distinct()
                .collect(toList()));
    }

    private List<StackTraceElement> addStackTrace(List<StackTraceElement> list, Set<String> includeTrace) {
        return list.stream()
                .filter(x -> include(includeTrace, x))
                .filter(x -> !"<generated>".equals(x.getFileName())
                        && !x.getClassName().startsWith("com.sprometheus"))
                .distinct()
                .collect(toList());
    }

    private boolean include(Set<String> filterTrace, StackTraceElement x) {
        for (String v : filterTrace) {
            if (x.getClassName().startsWith(v)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return createText();
    }
}
